package com.naza.rpc.circuitBreaker;

import com.naza.rpc.exception.ResourceBusyException;
import com.naza.rpc.exception.TimeoutException;
import com.naza.rpc.util.ClassUtils;
import com.naza.rpc.util.PropertiesUtil;
import com.naza.rpc.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * 熔断器仓库、配置
 * @author yl
 */
public class CircuitBreakerLoader {
    private static final Logger logger = LoggerFactory.getLogger(CircuitBreakerLoader.class);

    private static final String CLIENT_CB_KEY = "client.circuitBreaker";

    private static final String RETRY_CB_KEY = "retry.circuitBreaker";

    private static final int CLIENT_RING_BUFFER_SIZE_IN_CLOSE_STATUS = 15;

    private static final int CLIENT_FAILURE_RATE_THRESHOLD_IN_CLOSE_STATUS = 50;

    private static final int CLIENT_RING_BUFFER_SIZE_IN_HALF_OPEN_STATUS = 5;

    private static final int CLIENT_FAILURE_RATE_THRESHOLD_IN_HALF_OPEN_STATUS = 30;

    private static final int CLIENT_WAIT_DURATION_IN_OPEN_STATE = 1 * 60 * 1000;

    private static final int CLIENT_MAX_CONCURRENT_EXECUTIONS_IN_HALF_OPEN_STATE = 1;

    private static final Class[] CLIENT_FAIL_ON = new Class[]{ResourceBusyException.class,
        TimeoutException.class};

    private static final int RETRY_RING_BUFFER_SIZE_IN_CLOSE_STATUS = 20;

    private static final int RETRY_FAILURE_RATE_THRESHOLD_IN_CLOSE_STATUS = 40;

    private static final int RETRY_WAIT_DURATION_IN_OPEN_STATE = 5 * 60 * 1000;

    private static CircuitBreakerLoader instance = new CircuitBreakerLoader();

    // 客户端熔断器 缓存
    private ConcurrentMap<String, CircuitBreaker> clientBreakers = new ConcurrentHashMap<String, CircuitBreaker>();

    // 客户端重试熔断器缓存
    private ConcurrentMap<String, CircuitBreaker> retryBreakers = new ConcurrentHashMap<String, CircuitBreaker>();

    // 客户端熔断器开启标志位，对应client.circuitBreaker中disable
    private boolean enableClientCircuitBreaker = false;

    // 客户端重试熔断器开启标志位，对应retry.circuitBreaker中disable
    private boolean enableRetryCircuitBreaker = false;

    // 客户端熔断器监听器缓存
    private List<Listener> clientBreakerListeners = new ArrayList<Listener>();

    private CircuitBreakerLoader() {
        setDisableClientCircuitBreaker();
        setDisableRetryCircuitBreaker();
    }

    public void registerClientBreakerListener(Listener listener) {
        clientBreakerListeners.add(listener);
    }

    public static CircuitBreakerLoader getInstance() {
        return instance;
    }

    /**
     * 获取client-接口对应的熔断器
     *
     * @param clientId
     *            client id
     * @param serviceMethodId
     *            接口
     * @return 熔断器
     */
    public CircuitBreaker getClientCircuitBreaker(String clientId, String serviceMethodId) {
        String key = "CLIENT_" + clientId + "@" + serviceMethodId;
        CircuitBreaker cb = clientBreakers.get(key);
        if (cb != null) {
            return cb;
        }
        CircuitBreakerConfig config = setClientCircuitBreakerConfig();
        CircuitBreaker newCb = new CircuitBreakerImpl(key, config);
        CircuitBreaker existed = clientBreakers.putIfAbsent(key, newCb);
        return (existed != null) ? existed : newCb;
    }

    public void clearClientCircuitBreakers(String clientId) {
        List<String> needClearKey = new ArrayList<String>();
        String keyPrefix = "CLIENT_" + clientId + "@";
        for (String key : clientBreakers.keySet()) {
            if (key.startsWith(keyPrefix)) {
                needClearKey.add(key);
            }
        }
        for (String key : needClearKey) {
            clientBreakers.remove(key);
        }
    }

    /**
     * 获取接口对应的重试熔断器
     *
     * @param serviceMethodId
     *            接口
     * @return 重试熔断器
     */
    public CircuitBreaker getRetryCircuitBreaker(String serviceMethodId) {
        String key = "RETRY_" + serviceMethodId;
        CircuitBreaker cb = retryBreakers.get(key);
        if (cb != null) {
            return cb;
        }
        CircuitBreakerConfig config = setRetryCircuitBreakerConfig();
        CircuitBreaker newCb = new CircuitBreakerImpl(key, config);
        CircuitBreaker existed = retryBreakers.putIfAbsent(key, newCb);
        return (existed != null) ? existed : newCb;
    }

    public boolean disableClientCircuitBreaker() {
        return this.enableClientCircuitBreaker;
    }

    private void setDisableClientCircuitBreaker() {
        Properties properties = new Properties();
        try {
            String enable = PropertiesUtil.getINSTANCE().getValue("enable");
            if (StringUtils.isNotEmpty(enable)) {
                this.enableClientCircuitBreaker = Boolean.valueOf(enable.trim());
            }
        } catch (Exception ex) {
            logger.error("Exception", ex);
        }
    }

    /**
     * 是否开启客户端重试熔断器
     * @return
     */
    public boolean disableRetryCircuitBreaker() {
        return this.enableRetryCircuitBreaker;
    }

    private void setDisableRetryCircuitBreaker() {
        Properties properties = new Properties();
        try {
            String enable = PropertiesUtil.getINSTANCE().getValue("retryEnable");
            if (StringUtils.isNotEmpty(enable)) {
                this.enableRetryCircuitBreaker = Boolean.valueOf(enable.trim());
            }
        } catch (Exception ex) {
            logger.error("Exception", ex);
        }
    }

    /**
     * 设置熔断器配置
     * 
     * @return
     */
    private CircuitBreakerConfig setClientCircuitBreakerConfig() {
        // 先设置初始值
        //failureThresholdInClose 前面那个值
        int ringBufferSizeInClose = CLIENT_RING_BUFFER_SIZE_IN_CLOSE_STATUS;
        //failureThresholdInClose 后面那个值  失败百分比进入熔断关闭状态
        int failRateInClose = CLIENT_FAILURE_RATE_THRESHOLD_IN_CLOSE_STATUS;
        int ringBufferSizeInHalfOpen = CLIENT_RING_BUFFER_SIZE_IN_HALF_OPEN_STATUS;
        // failureThresholdInHalfOpen 后面那个值  失败百分比进入熔断半开状态
        int failRateInHalfOpen = CLIENT_FAILURE_RATE_THRESHOLD_IN_HALF_OPEN_STATUS;
        //duration
        int duration = CLIENT_WAIT_DURATION_IN_OPEN_STATE;
        //maxConcurrentInHalfOpenState
        int maxConcurrent = CLIENT_MAX_CONCURRENT_EXECUTIONS_IN_HALF_OPEN_STATE;
        Class[] failOn = CLIENT_FAIL_ON;
        try {
            String failInClose = PropertiesUtil.getINSTANCE().getValue("failInClose");
            if (StringUtils.isNotEmpty(failInClose)) {
                String[] array = StringUtils.split(failInClose, ",");
                if (array != null && array.length == 2) {
                    ringBufferSizeInClose = Integer.valueOf(array[0].trim());
                    // 失败次数进入熔断关闭状态
                    failRateInClose = Integer.valueOf(array[1].trim());
                }
            }
            String failInHalfOpen = PropertiesUtil.getINSTANCE().getValue("failInHalfOpen");
            if (StringUtils.isNotEmpty(failInHalfOpen)) {
                String[] array = StringUtils.split(failInHalfOpen, ",");
                if (array != null && array.length == 2) {
                    ringBufferSizeInHalfOpen = Integer.valueOf(array[0].trim());
                    // 失败次数进入熔断关闭状态
                    failRateInHalfOpen = Integer.valueOf(array[1].trim());
                }
            }
            String durationStr = PropertiesUtil.getINSTANCE().getValue("duration");
            if (StringUtils.isNotEmpty(durationStr)) {
                duration = Integer.valueOf(durationStr.trim());
            }
            String maxStr = PropertiesUtil.getINSTANCE().getValue("maxConcurrentInHalfOpen");
            if (StringUtils.isNotEmpty(maxStr)) {
                maxConcurrent = Integer.valueOf(maxStr.trim());
            }
            String failOnStr = PropertiesUtil.getINSTANCE().getValue("failOn");
            if (StringUtils.isNotEmpty(failOnStr)) {
                String[] array = StringUtils.split(failOnStr, ",");
                if (array != null) {
                    Class[] clzA = new Class[array.length];
                    for (int i = 0; i < array.length; i++) {
                        clzA[i] = ClassUtils.forName(array[i].trim());
                    }
                    failOn = clzA;
                }
            }
        } catch (Exception ex) {
            logger.error("Exception", ex);
        }
        return CircuitBreakerConfig.custom().ringBufferSizeInClosedState(ringBufferSizeInClose)
                .failureRateThresholdInClosedStatus(failRateInClose).hasHalfOpenState(true)
                .ringBufferSizeInHalfOpenState(ringBufferSizeInHalfOpen)
                .failureRateThresholdInHalfOpenStatus(failRateInHalfOpen).waitDurationInOpenState(duration)
                .maxConcurrentExecutionsInHalfOpenState(maxConcurrent).failOn(failOn).build();
    }

    /**
     * 设置熔断器重试配置
     * @return
     */
    private CircuitBreakerConfig setRetryCircuitBreakerConfig() {
        Properties properties = new Properties();
        int ringBufferSizeInClose = RETRY_RING_BUFFER_SIZE_IN_CLOSE_STATUS;
        int failRateInClose = RETRY_FAILURE_RATE_THRESHOLD_IN_CLOSE_STATUS;
        int duration = RETRY_WAIT_DURATION_IN_OPEN_STATE;
        try {
            String failureThresholdInClose = PropertiesUtil.getINSTANCE().getValue("retryFailInClose");
            if (StringUtils.isNotEmpty(failureThresholdInClose)) {
                String[] array = StringUtils.split(failureThresholdInClose, ",");
                if (array != null && array.length == 2) {
                    ringBufferSizeInClose = Integer.valueOf(array[0].trim());
                    //重试熔断次数
                    failRateInClose = Integer.valueOf(array[1].trim());
                }
            }
            String durationStr = PropertiesUtil.getINSTANCE().getValue("retryDuration");
            if (StringUtils.isNotEmpty(durationStr)) {
                duration = Integer.valueOf(durationStr.trim());
            }
        } catch (Exception ex) {
            logger.error("Exception", ex);
        }
        return CircuitBreakerConfig.custom().ringBufferSizeInClosedState(ringBufferSizeInClose)
                .failureRateThresholdInClosedStatus(failRateInClose).hasHalfOpenState(false)
                .waitDurationInOpenState(duration).build();
    }

    public interface Listener {
        void onClearBreakers();
    }
}
